package tree;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

public class Node {

	private Node parent;
	private Node suffixLink;
	private int startIndex;
	private int endIndex;
	private List<Node> children;
	private int depth;
	
	//For finding tandeem repeats.
	private boolean marked;

	private int upperInterval;
	private int lowerInterval;
	/**
	 * Constructor of a node. Instantiation children List
	 * 
	 * We do not instantiate parent and suffixlink, as these are handled
	 * elsewhere
	 * 
	 * parent are handled in addChild, setting the parent of the children to
	 * this.
	 * 
	 * SuffixLink are set and used by the Tree class, and when the tree is constructed.
	 * 
	 * @param startIndex
	 *            the index where this node starts inclusive
	 * @param endIndex
	 *            the index where this node ends inclusive
	 */

	public Node(int startIndex, int endIndex) {
		this.children = new ArrayList<Node>();
		this.startIndex = startIndex;
		this.endIndex = endIndex;
		this.depth = 0;
		this.marked = false;
	}

	public boolean isLeaf() {
		return children.isEmpty();
	}

	public Node getParent() {
		return parent;
	}

	public Node getSuffixLink() {
		return suffixLink;
	}

	public void setSuffixLink(Node suffixLink) {
		this.suffixLink = suffixLink;
	}

	public int getStartIndex() {
		return startIndex;
	}

	public void setStartIndex(int startIndex) {
		this.startIndex = startIndex;
	}

	public int getEndIndex() {
		return endIndex;
	}

	public void setEndIndex(int endIndex) {
		this.endIndex = endIndex;
	}

	/**
	 * Adds the node to the tree TODO: what is the index and length of the
	 * child?
	 * 
	 * @param n
	 *      	the node that should be added
	 */
	public void addChildren(Node n) {
		if(n.parent != null){
			n.parent.removeChildren(n);
		}
		n.parent = this;
		n.depth = this.depth + n.length();
		children.add(n);
	}

	public void removeChildren(Node n) {
		if(n.parent != this) return;
		children.remove(n);
		n.parent = null;
	}

	/**
	 * Calculates the length of the node
	 * @return the length of the node.
	 */
	public int length() {
		return endIndex - startIndex + 1;
	}
	
	public int getDepth(){
		return depth;
	}

	/**
	 * @param beginIndex
	 * @param endIndex
	 * @return the sub string of the working string from beginIndex to endIndex
	 */
	public String subString(String s) {
		return s.substring(startIndex, endIndex + 1);
	}

	public List<Node> getChildren() {
		return children;
	}
	
	public boolean isMarked(){
		return marked;
	}

	public void setMarked() {
		this.marked = true;
	}

	public int getUpperInterval() {
		return upperInterval;
	}

	public void setUpperInterval(int upperInterval) {
		this.upperInterval = upperInterval;
	}

	public int getLowerInterval() {
		return lowerInterval;
	}

	public void setLowerInterval(int lowerInterval) {
		this.lowerInterval = lowerInterval;
	}
	
	public List<Node> getLeafList(List<Node> res){
		if(this.isLeaf()){
			res.add(this);
			return res;
		} else {
			for(Node n : this.getChildren()){
				res = n.getLeafList(res);
			}
			return res;
		}
	}

	public int[] makeLookupTable(List<Node> leaves, Map<Integer, Node> map) {
		if(isLeaf()){
			for(Node n:leaves){
				if(this == n){
					int res[] = {0,0};
					res[1] = res[0] = leaves.indexOf(n);
					upperInterval = res[1];
					lowerInterval = res[0];
					map.put(upperInterval, this);
					return res;
				}
			}
			throw new InternalError("Leaf not found in leafTable");
		} else {
			int res[] = {0,0};
			upperInterval = 0;
			lowerInterval = leaves.size();
			for(Node n : this.getChildren()){
				int tmp[] = n.makeLookupTable(leaves, map);
				if(tmp[1]>upperInterval){
					upperInterval = tmp[1];
				}
				if(tmp[0]<lowerInterval){
					lowerInterval = tmp[0];
				}
			}
			res[1] = upperInterval;
			res[0] = lowerInterval;
			return res;
		}
	}

	public void getInternalnodes(List<Node> res) {
		if(this.isLeaf()) return;
		res.add(this);
		for(Node n: children){
			n.getInternalnodes(res);
		}
	}
}
